// Ofri Keidar 302933981 group 05

/**
 * This class implements View interface. The class implements communication with user via console.
 * User is asked for wanted options and the class uses the controller to perform the tasks
 */

package view;

import controller.Controller;
import controller.Transactor;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import model.apartment.Apartment;
import model.apartment.Flat;
import model.apartment.GardenApartment;
import model.apartment.Penthouse;

public class UserInterface implements View {
	
	// members
	private static final int FLOOR_DUMMY = 1;
	enum SysModes{ADD_BLD, SHOW_INFO, UPDATE_RSDNT, SHUT_DOWN} // holds system modes
	enum Queries{GET_VACANT_FLATS, GET_VACANT_FLATS_BY_ROOMS, GET_VACANT_FLATS_BY_ROOMS_AND_ADRS, GET_FLATS_BY_ADRS,
		GET_ALL_FLATS, GET_ALL_PENTHOUSES, GET_ALL_GARDEN_APRTMNTS, GET_NUM_OF_VACANT_APRTS, GET_NUM_OF_VACANT_APRTS_BY_ADRS} // holds queries that can be done
	private Controller transactor; // used to perform the tasks
	
	// methods

	/**
	 * Prints main menu (modes) to the screen, so the user can choose wanted mode
	 */
	private void showMainMenu() {
		
		// print modes and their numeric values
		System.out.println("Please choose one of the following:");
		System.out.println((SysModes.ADD_BLD.ordinal()+1) + ". Add a new building");
		System.out.println((SysModes.SHOW_INFO.ordinal()+1) + ". Show info");
		System.out.println((SysModes.UPDATE_RSDNT.ordinal()+1) + ". Update resident's details");
		System.out.println((SysModes.SHUT_DOWN.ordinal()+1) + ". Shut down");
	}
	
	/**
	 * Prints queries to the screen, so the user can choose wanted query to database
	 */
	private void showQueries() {
		
		// print queries and their numeric values
		System.out.println("Please insert query number:");
		System.out.println((Queries.GET_VACANT_FLATS.ordinal()+1) + ". Show details of all vacant flats in system");
		System.out.println((Queries.GET_VACANT_FLATS_BY_ROOMS.ordinal()+1) + ". Show details of all vacant flats with certain room number");
		System.out.println((Queries.GET_VACANT_FLATS_BY_ROOMS_AND_ADRS.ordinal()+1) + ". Show details of all vacant flats with certain room number at certain address");
		System.out.println((Queries.GET_FLATS_BY_ADRS.ordinal()+1) + ". Show details of all flats at certain address");
		System.out.println((Queries.GET_ALL_FLATS.ordinal()+1) + ". Show details of all flats in system");
		System.out.println((Queries.GET_ALL_PENTHOUSES.ordinal()+1) + ". Show details of all penthouses at certain address");
		System.out.println((Queries.GET_ALL_GARDEN_APRTMNTS.ordinal()+1) + ". Show details of all garden apartments at certain address");
		System.out.println((Queries.GET_NUM_OF_VACANT_APRTS.ordinal()+1) + ". Find how many vacant apartments are in system");
		System.out.println((Queries.GET_NUM_OF_VACANT_APRTS_BY_ADRS.ordinal()+1) + ". Find how many vacant apartments are at certain address");
	}
	
	/**
	 * Asks user for building's address and parameters for all building's apartments.
	 * If there is no other building at given address and if the parameters for each apartment were given in 
	 * correct format (checked according to each apartment's type), a new building with these details is created.
	 */
	private void addBuilding() {
		
		// initialize a list to hold parameters of each apartment
		LinkedList< Map<String, String> > parameterList = new LinkedList<Map<String,String>>();
		// initialize a list of types- i'th element is type of i'th apartment
		LinkedList<String> typeList = new LinkedList<String>();
		
		boolean keepAddingApartments = true; // indicates if user wishes to add more apartments to the new building
		 
		while (keepAddingApartments) {
	 
			// ask user for apartment's details, keep asking until details are given in correct format
			 
			boolean invalidApartmentDetails = true; // indicates if details for apartment were given in a valid format or not
			 
			while (invalidApartmentDetails) {
				
				// initialize parameters strings
				String parameters = "";
				String apartmentType;
				
				while (true) {
					// get apartmetn's type
					apartmentType = Console.readString("Please enter apartment type: Flat, GardenApartment, Penthouse\n");			
					
					// ask for parameters according to given type
					if (apartmentType.equals("Flat")) {
						parameters = Console.readString("Please insert: floor, room number, size, warehouse area and resident name (optional)\n");
						break;
						
					} else if (apartmentType.equals("Penthouse")) {
						parameters = Console.readString("Please insert: floor, room number, size, balcony area and resident name (optional)\n");
						break;
						
					} else if (apartmentType.equals("GardenApartment")) {
						// garden apartment's floor is fixed, add a dummy value to parse parameters correctly
						parameters = FLOOR_DUMMY + " " + Console.readString("Please insert: room number, size, garden area, resident name (optional) and separate entrance (true/false)\n");					

						break;
						
					} else {
						System.out.println("Given type is not known by the system, please try again.");
					}
				}
				
				// split string to tokens
				String delims  = "[ ]";
				String[] paramTokens = parameters.split(delims);
				
				// try parsing the parameters according to given apartment type
				Map<String, String> parameterMap = transactor.parseParameters(apartmentType, paramTokens);			
				if (parameterMap.isEmpty()) {
					// could not parse, format is invalid
					System.out.println("Invalid format for parameters, please try again.");
					
				} else {

					// add parsed parameters to parameter list
					parameterList.add(parameterMap);
					// add type to type list
					typeList.add(apartmentType);
					// all parameters for apartment were given correctly
					invalidApartmentDetails = false;
				}
			}
			 
			if (Console.readDouble("If you wish to add another apartment to the building, press any number. Otherwise, please insert 0\n") == 0) {
				keepAddingApartments = false;
			}
			 
		}
		 
		// get building's address
		String stName = Console.readString("Please insert street name\n");
		int bldNumber = Console.readInt("Please insert building number\n");
		
		// add a new building according to given details
		if (transactor.addBuilding(typeList, parameterList, stName, bldNumber) == false) {
			// notify user if could not be created
			System.out.println("Could not add a building at " + bldNumber + " " + stName);
		}
	}
	
	/**
	 * Gets a map of apartments and prints each apartment (map's key) and its address (map's value)
	 * @param apartmentsMap- apartments map to print
	 */
	private void printApartments(Map<Apartment, String> apartmentsToPrint) {	
		// scan given map and print each apartment
		for (Apartment currApartment : apartmentsToPrint.keySet()) {
			// print current apartment and its address
			System.out.println(apartmentsToPrint.get(currApartment) + ", " + currApartment);
		}		
	}
	
	/**
	 * Shows user queries menu and asks the controller object to carry out given query
	 */
	private void runQuery() {

		// holds numeric value of query
		int query;
		
		// keep asking user for query, stop asking when given value is valid
		while (true) {
			
			// show possible queries to user
			showQueries(); 
			// get user's choice
			query = Console.readInt(); 
			// check if given option is a possible value
			if (query < 0 || Queries.values().length < query) {
				// invalid option
				System.out.println("Invalid option, please try again.");
			} else {
				// valid option is given, stop looping
				break;				
			}
		}
		
		// find which option user has chosen
		Queries choise = Queries.values()[query-1];
		
		// perform task given by user
		switch (choise) {
		
		case GET_VACANT_FLATS: {
			
			// get all vacant flats in system
			Map<Flat, String> vacantFlats = transactor.getVacantFlats();
			
			// if there are not any, notify user
			if (vacantFlats.isEmpty()) {
				System.out.println("No vacant flats in system");
				break;
			}
			
			// flats were returned, scan given map and print each flat
			Map<Apartment, String> apartmentToPrint = new TreeMap<Apartment, String>();
			apartmentToPrint.putAll(vacantFlats);
			printApartments(apartmentToPrint);
			break;
		}
		
		case GET_VACANT_FLATS_BY_ROOMS: {
			
			// ask user for room number to search
			int roomNumber = Console.readInt("Please insert room number to search:\n");
			
			// get all vacant flats with given room number in system
			Map<Flat, String> vacantFlats = transactor.getVacantFlats(roomNumber);
			
			// if there are not any, notify user
			if (vacantFlats.isEmpty()) {
				System.out.println("No vacant flats with " + roomNumber + " rooms in system");
				break;
			}
			
			// flats were returned, scan given map and print each flat
			Map<Apartment, String> apartmentsToPrint = new TreeMap<Apartment, String>();
			apartmentsToPrint.putAll(vacantFlats);
			printApartments(apartmentsToPrint);			
			break;
		}
		
		case GET_VACANT_FLATS_BY_ROOMS_AND_ADRS: {
			
			// ask user for room number and address to search
			int roomNumber = Console.readInt("Please insert room number to search:\n");
			String stName = Console.readString("Please insert street name:\n");
			int bldNumber = Console.readInt("Please insert building number:\n");
			
			// get all vacant flats with given room number at given address
			Map<Flat, String> vacantFlats = transactor.getVacantFlats(roomNumber, stName, bldNumber);
			
			// if there are not any, notify user
			if (vacantFlats.isEmpty()) {
				System.out.println("No vacant flats with " + roomNumber + " rooms at " + bldNumber + " " + stName);
				break;
			}
			
			// flats were returned, scan given map and print each flat
			Map<Apartment, String> apartmentsToPrint = new TreeMap<Apartment, String>();
			apartmentsToPrint.putAll(vacantFlats);
			printApartments(apartmentsToPrint);
			break;
		}
		
		case GET_FLATS_BY_ADRS: {
			
			// ask user for address to search
			String stName = Console.readString("Please insert street name:\n");
			int bldNumber = Console.readInt("Please insert building number:\n");
			
			// get all flats at given address
			Map<Flat, String> flats = transactor.getFlats(stName, bldNumber);
			
			// if there are not any, notify user
			if (flats.isEmpty()) {
				System.out.println("No flats at " + bldNumber + " " + stName);
			}
			
			// flats were returned, scan given map and print each flat
			Map<Apartment, String> apartmentsToPrint = new TreeMap<Apartment, String>();
			apartmentsToPrint.putAll(flats);
			printApartments(apartmentsToPrint);		
			break;
		}
			
		case GET_ALL_FLATS: {
			
			// get all flats in system
			Map<Flat, String> systemFlats = transactor.getFlats();
			
			// if there are not any, notify user
			if (systemFlats.isEmpty()) {
				System.out.println("No flats in system");
				break;
			}
			
			// flats were returned, scan given map and print each flat
			Map<Apartment, String> apartmentsToPrint = new TreeMap<Apartment, String>();
			apartmentsToPrint.putAll(systemFlats);
			printApartments(apartmentsToPrint);		
			break;
		}
			
		case GET_ALL_PENTHOUSES: {
			
			// get all penthouses in system
			Map<Penthouse, String> systemPenthouses = transactor.getPenthouses();
			
			// if there are not any, notify user
			if (systemPenthouses.isEmpty()) {
				System.out.println("No penthouses in system");
				break;
			}
			
			// penthouses were returned, scan given map and print each penthouse
			Map<Apartment, String> apartmentsToPrint = new TreeMap<Apartment, String>();
			apartmentsToPrint.putAll(systemPenthouses);
			printApartments(apartmentsToPrint);		
			break;
		}
			
		case GET_ALL_GARDEN_APRTMNTS: {
			
			// get all garden apartments in system
			Map<GardenApartment, String> systemGardenAprts = transactor.getGardenApartments();
			
			// if there are not any, notify user
			if (systemGardenAprts.isEmpty()) {
				System.out.println("No garden apartments in system");
				break;
			}
			
			// garden apartments were returned, scan given map and print each garden apartment
			Map<Apartment, String> apartmentsToPrint = new TreeMap<Apartment, String>();
			apartmentsToPrint.putAll(systemGardenAprts);
			printApartments(apartmentsToPrint);		
			
			break;
		}
			
		case GET_NUM_OF_VACANT_APRTS: {
			// print how many vacant apartments there are in entire system			
			System.out.println("Number of vacant apartments is system: " + transactor.getVacantAprtsNum());
			break;
		}
			
		case GET_NUM_OF_VACANT_APRTS_BY_ADRS: {
			
			// ask user for building's address 
			String stName = Console.readString("Please insert street name:\n");
			int bldNumber = Console.readInt("Please insert building number:\n");
			
			// print how many vacant apartments there are in entire system			
			System.out.println("Number of vacant apartments at " + bldNumber + " " + stName + ": " + transactor.getVacantAprtsNum(stName, bldNumber));
			break;
		}
		
		}
	}
	
	/**
	 * Asks user for address, apartment number and new resident name.
	 * If there is no building at given address or no apartment with given number in the building, user is asked
	 * again for details.
	 * Otherwise, sets given name as the wanted apartment's resident name
	 */
	private void updateResident() {
		
		// ask user for address and apartment number 
		String stName = Console.readString("Please insert street name:\n");
		int bldNum = Console.readInt("Please insert building number:\n");
		int apartmentNum = Console.readInt("Please insert apartment number:\n");
		// ask user for new resident name
		String newResident = Console.readString("Please insert new resident name:\n");
		
		// try updating resident
		boolean couldUpdate = transactor.updateResident(stName, bldNum, apartmentNum, newResident);
		if (!couldUpdate) {
			System.out.println("Could not update resident- please check your address or apartment number.");
		}
	}
	
	/**
	 * Constructor, initializes a new user-interface object
	 */
	public UserInterface() {

		try {
			
			// try allocating a new controller object
			transactor = new Transactor();
			
		} catch (Exception e) {
			
			System.out.println("Error- could not initialize databse!");
		}  
	}
	
	/**
	 * Prints options for user and performs user's choices. 
	 * When user wishes to exit program, data is saved.
	 */
	public void startRunning() {

		int option; // hold user chosen option
		boolean keepRunning = true; // indicates if should keep asking user for options
		
		// keep looping and asking user for options
		while (keepRunning) {
			
			// keep looping until option given by user is valid
			while (true) {
				
				// show main menu
				showMainMenu(); 
				// get user's choice
				option = Console.readInt(); 
				// check if given option is a possible value				
				if (option < 0 || SysModes.values().length < option) {
					// invalid option
					System.out.println("Invalid option, please try again.");
				} else {
					// valid option is given, stop looping
					break;				
				}
			}
			
			// find which option user has chosen
			SysModes choise = SysModes.values()[option-1];
			
			// perform task given by user
			switch (choise) {
			case ADD_BLD:
				// add a new building
				addBuilding();
				break;
	
			case SHOW_INFO:
				// ask user for wanted query and perform task
				runQuery();			
				break;
				
			case UPDATE_RSDNT:
				// get apartment's details and update resident according to user
				updateResident();
				break;
				
			case SHUT_DOWN:
				
				// shut system down
				boolean shutDownOK = transactor.shutDown();
				
				// check success of shutting down
				if (shutDownOK) {
					// program is ready to shut down, end loop
					keepRunning = false;
				} else {
					System.out.println("An error occourred while trying to shut down, please try again later.");
				}
				
				break;

			}
		
		}
		
	}

}
